from lxml import etree
from itertools import groupby
import ast
import inspect
import os

class XmlSerializer(object):

    def __init__(self, rootModuleList=None, specificModuleList=None):
        '''
        Constructor
        '''
        if rootModuleList is None:
            rootModuleList = []
        if specificModuleList is None:
            specificModuleList = []
                    
        self.classMap = {}
        
        for curModule in rootModuleList:
            self._AddClassesInModuleAndSubModules(curModule)
            
        for curModule in specificModuleList:
            self._AddClassesInModule(curModule)
        
    def _AddClassesInModuleAndSubModules(self, curModule):
        self._AddClassesInModule(curModule)
        
        moduleList = inspect.getmembers(curModule, inspect.ismodule)
        for moduleName, moduleType in moduleList:
            self._AddClassesInModuleAndSubModules(moduleType)
            
    def _AddClassesInModule(self, curModule):
        classList = inspect.getmembers(curModule, inspect.isclass)
        for className, classType in classList:
            self.classMap[className] = classType
        
        
        
    def Serialize(self, obj):
        root = etree.Element("root")
        self._Serialize(root, obj, obj.__class__.__name__)
        return etree.tostring (root[0], pretty_print=True )
        
    
    def _Serialize(self, parent, var, varName):
        child = etree.SubElement(parent, varName)
        setTypeAttr = False
        
                    
        if hasattr(var, "__dict__"):
            setTypeAttr = True
            for key, value in var.__dict__.items():
                self._Serialize(child, value, str(key))

        elif isinstance(var, dict):
            setTypeAttr = True
            for key, value in var.items():
                dictEntryChild = etree.SubElement(child, varName + "-entry")
                self._Serialize(dictEntryChild, key, "key")
                self._Serialize(dictEntryChild, value, "value")
                                
        elif hasattr(var, "__iter__"):
            setTypeAttr = True
            for value in var:
                self._Serialize(child, value, varName + "-element")

        else:
            setTypeAttr = False
            child.set("value", str(var))

        if setTypeAttr == True:
            child.set("type", type(var).__name__)

    def DeserializeFromFile(self, filePath):
        xmlFile = open(filePath)
        xmlStr = xmlFile.read()
        xmlFile.close()
        
        backupCwd = os.getcwd()
        self.ChangeCwdToContainingFolder(filePath)
        
        rootObj = self.Deserialize(xmlStr)
        
        os.chdir(backupCwd)
        
        return rootObj
        
  
    def Deserialize(self, xmlString):
        xmlString = self.FlattenFileAttributes(xmlString)
        tree = etree.XML(xmlString)
        return self._Deserialize(tree)
    
    def FlattenFileAttributes(self, xmlString):
        root = etree.Element("root")
        tree = etree.XML(xmlString)
        root.append(tree)
        
        self._FlattenFileAttributes(root[0])
        return etree.tostring(root[0], pretty_print=True )
        
    def _FlattenFileAttributes(self, xmlTree):
        parentNode = xmlTree.getparent()
        
        attribs = dict(xmlTree.attrib)
        if "file" in attribs:
            filePath = attribs["file"]
            
            configFile = open(filePath)
            fileAsStr = configFile.read()
            configFile.close()
            externalTree = etree.XML(fileAsStr)
            
            parentNode.replace(xmlTree, externalTree)
            
            backupCwd = os.getcwd()
            self.ChangeCwdToContainingFolder(filePath)
            
            self._FlattenFileAttributes(externalTree)
            
            os.chdir(backupCwd)
        else:
            for childNode in xmlTree.iterchildren():
                self._FlattenFileAttributes(childNode)
        
    
    def ChangeCwdToContainingFolder(self, filePath):
        absPath = os.path.abspath(filePath)
        curCwd = os.path.dirname(absPath)
        os.chdir(curCwd)
    
    def _Deserialize(self, xmlTree):
        attribs = dict(xmlTree.attrib)
        if "value" in attribs:
            value = None
            try:
                value = ast.literal_eval(attribs["value"])
            except:
                value = attribs["value"]
            return value
        
        else:
            curObj = None

            #Initialize curObj            
            if "type" in attribs:
                objType = attribs["type"]

                if objType == "dict":
                    curObj = {}
                elif objType == "list":
                    curObj = []
                elif objType == "set":
                    curObj = set([])
                else:
                    curClass = self.classMap[attribs["type"]]
                    curObj = curClass.__new__(curClass)

            #Treat the current object as a dictionary if no type is specified
            else:
                curObj = {}
            
            for varName, varItter in groupby(xmlTree, lambda x: x.tag):
                
                #This can occur when parsing comment tags.
                if not isinstance(varName, str):
                    continue
                
                for xmlSubTree in varItter:
                    childObj = self._Deserialize(xmlSubTree)
                    
                    if "type" in attribs:
                        objType = attribs["type"]

                        if objType == "dict":
                            curObj[childObj["key"]] = childObj["value"]
                        elif objType == "list":
                            curObj.append(childObj)
                        elif objType == "set":
                            curObj.add(childObj)
                        else:
                            setattr(curObj, varName, childObj)                            
                    else:
                        curObj[varName] = childObj
        
            return curObj
        